#include <tinx/fb.h>
#include <tinx/device.h>
#include <tinx/memory.h>
#include <tinx/stdlib.h>
#include <tinx/string.h>
#include <tinx/assert.h>

static fb_op_t *fb_ops[16];

static framebuffer_t fbdev;
framebuffer_t *fb = &fbdev;

fb_op_t *fb_get_op(int type)
{
    return fb_ops[type];
}

void fb_register_op(int type, fb_op_t *op)
{
    assert(!fb_ops[type]);
    fb_ops[type] = op;
}

// 未完善
int fb_ioctl(framebuffer_t *fb, int cmd, u32 arg)
{
    switch (cmd)
    {
    case FBIOGET_VSCREENINFO:
        fb_var_screeninfo_t *vinfo = (fb_var_screeninfo_t *)arg;
        memset(vinfo, 0, sizeof(fb_var_screeninfo_t));
        vinfo->xres = fb->width;
        vinfo->yres = fb->height;
        vinfo->bits_per_pixel = fb->bpp;
        vinfo->xres_virtual = fb->width;
        vinfo->yres_virtual = fb->height;
        return EOK;
    case FBIOGET_FSCREENINFO:
        fb_fix_screeninfo_t *finfo = (fb_fix_screeninfo_t *)arg;
        memset(finfo, 0, sizeof(fb_fix_screeninfo_t));
        strcpy(finfo->id, "BIOS");
        finfo->smem_start = fb->fb_paddr;
        finfo->smem_len = fb->height * fb->width * (fb->bpp / 8);
        finfo->line_length = fb->width * (fb->bpp / 8);
        return EOK;
    default:
        return -ENOSYS;
    }
}

int fb_get_prime_op(fb_op_t **op)
{
    // fb_ops[0] 始终保留
    for (int i = 1; i < FB_TYPE_NUM; i++)
    {
        fb_op_t *ptr = fb_ops[i];
        if (op)
        {
            *op = ptr;
            return i;
        }
    }
    return -ENOFB;
}

void unmap_framebuffer()
{
    if (fb->fb_vaddr)
    {
        u32 count = div_round_up(fb->fb_len, PAGE_SIZE);
        unmap_page(fb->fb_vaddr, count);
    }
}

void map_framebuffer()
{
    map_area(fb->fb_paddr, fb->fb_len);
    fb->fb_vaddr = fb->fb_paddr;
}

int fb_set_mode(int width, int height, int bpp)
{
    int ret = fb->op->set_mode(width, height, bpp);
    if (ret < 0)
    {
        return ret;
    }

    unmap_framebuffer();

    fb->bpp = bpp;
    fb->width = width;
    fb->height = height;

    fb->fb_paddr = fb->op->get_framebuffer();
    fb->fb_len = width * height * (bpp / 8);

    map_framebuffer();

    return ret;
}

extern void vesa_init();
extern void fbcon_init();

void fb_init()
{
    memset(fb, 0, sizeof(framebuffer_t));
    memset(fb_ops, 0, sizeof(fb_ops));

    vesa_init();

    fb_op_t *op;
    int type = fb_get_prime_op(&op);
    if (op < 0)
        return;

    fb->op = op;
    fb->type = type;

    fb_set_mode(800, 600, 32);

    fbcon_init();
}